/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import io.ByteReader;
import io.ByteWriter;
import io.PacketInputStream;
import io.PacketOutputStream;
import packet.RawPacket;
import packet.in.NormalIMPacket;
import util.Crypter;
import util.Util;

import java.io.IOException;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Project  : rtx
 * Author   : solosky < solosky772@qq.com >
 * Created  : 4/7/14
 * License  : Apache License 2.0
 */
public class RtxClient {
    private PacketInputStream in;
    private PacketOutputStream out;
    private Socket sk;
    private short nextSeq = 0x0e55;
    private int uin;
    private String uname;
    private String pass;
    private byte[] touchKey, sessionKey;
    private Crypter crypter = new Crypter();
    private Timer timer = new Timer();

    public void login(String host, int port, String userName, String password) throws IOException {
        sk = new Socket(host, port);
        in = new PacketInputStream(sk.getInputStream());
        out = new PacketOutputStream(sk.getOutputStream());
        pass = password;

        ByteWriter writer = new ByteWriter();
        writer.writeByte(0x02);
        writer.writeUnicode(userName);
        touchKey = Util.randomKey();

        writePacket(0x400, writer.toByteArray(), touchKey, true);
    }


    private void writePacket(int cmd, byte[] data, byte[] key, boolean sendKey) throws IOException {
        System.out.println( "data=" + Util.convertByteToHexString(data));
        System.out.println( "key=" + Util.convertByteToHexString(key));

        byte[] raw = crypter.encrypt(data, key);
        if(sendKey){
            ByteWriter writer = new ByteWriter();
            writer.write(key);
            writer.writeByte(raw.length);
            writer.write(raw);
            raw = writer.toByteArray();
        }

        System.out.println( "raw=" + Util.convertByteToHexString(raw));

        RawPacket p = new RawPacket();
        p.setCmd(cmd);
        p.setSeq(nextSeq++);
        p.setData(raw);
        p.setUin(uin);
        p.setLen(raw.length + 0xe);
        out.writePacket(p);
    }



    private void processTouchReply(RawPacket packet) throws IOException {
        byte[] data = crypter.decrypt(packet.getData(), touchKey);
        System.out.println("touch reply data: "+ Util.convertByteToHexString(data));
        ByteReader reader = new ByteReader(data);
        int status = reader.readByte();
        if(status == 0xFE){
            System.out.println("user not exists!!!");
        } if(status == 0x02 || status == 0x0){
            uin = reader.readInt();
            uname = reader.readUnicode();
            byte[] salt = new byte[4];
            reader.read(salt);
            System.out.println("Touch OK : uin=" + uin +", uname=" +uname+",salt="+Util.convertByteToHexString(salt));

            ByteWriter writer = new ByteWriter();
            writer.writeUnicode(uname);
            writer.writeByte(0x01); //unknown
            writer.writeToken(crypter.encrypt(salt, passKey2(pass)));
            writer.writeToken(crypter.encrypt(salt, passKey1(pass)));
            writer.write(Util.randomKey()); //???
            writer.writeByte(0);

            writePacket(0x0412, writer.toByteArray(), Util.randomKey(), true);
        }else{
            System.out.println("Unknown touch result code: " + status);
        }
    }



    private void processVerifyReply(RawPacket packet) {
        byte []data = packet.getData();
        int status =data[0];
        if(status == 0){
            byte[] raw = new byte[packet.getLen() - 0xe -1];
            System.arraycopy(data,1, raw, 0, raw.length);
            byte[] plain = crypter.decrypt(raw, passKey2(pass));
            if(plain != null){
                ByteReader reader = new ByteReader(plain);
                reader.readInt(); //uin
                sessionKey = new byte[0x10];
                reader.read(sessionKey);

                System.out.println("LoginOK: sessionKey="+ Util.convertByteToHexString(sessionKey));

                startKeepAlive();

            }else{
                System.out.println("Decrypt data Failed!!!!");
            }
        }else{
            System.out.println("Login Failed: status=" + status );
        }
    }

    private void processNormalIM(RawPacket packet) throws IOException {
        NormalIMPacket imPacket = new NormalIMPacket(decryptPacket(packet, sessionKey));
        System.out.println(imPacket);

        ByteWriter writer = new ByteWriter();
        writer.write(Util.convertHexStringToByte("00 00 00 00 00 00 01"));
        writer.writeShort(imPacket.getImId());
        writer.writeUnicode(imPacket.getImType());
        writer.writeByte(0);
        writer.writeUnicode(imPacket.getImType2());
        writer.writeUnicode(imPacket.getTo());
        writer.writeUnicode(imPacket.getFrom());
        writer.write(Util.convertHexStringToByte("00 06 4F 00 4B 00 00 00"));

        writePacket(0xc02, writer.toByteArray(), sessionKey, false);
    }



    private RawPacket decryptPacket(RawPacket rawPacket, byte[] key){
        byte [] data = rawPacket.getData();
        data = crypter.decrypt(data, key);
        System.out.println("decryptPacket: " + Util.convertByteToHexString(data));
        rawPacket.setData(data);
        return rawPacket;
    }

    private void startKeepAlive(){
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    writePacket(0x0805, new byte[]{}, sessionKey, false);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 20*1000);
    }


    private void process(RawPacket packet) throws IOException {
        System.out.println("cmd=" + Integer.toHexString(packet.getCmd()) + ", " + Util.convertByteToHexString(packet.getData()));
        switch(packet.getCmd()) {
            case 0x400: processTouchReply(packet); break;
            case 0x412: processVerifyReply(packet); break;
            case 0xc01: processNormalIM(packet); break;
        }
    }



    private byte[] passKey1(String pass) {
       return md5(pass.getBytes());
    }

    private byte[] passKey2(String pass){
        byte[] pp = pass.getBytes();
        byte[] dd = new byte[0x10];
        Arrays.fill(dd, (byte) 0);
        System.arraycopy(pass.getBytes(), 0, dd, 0, Math.min(pp.length, 15));
        return md5(dd);
    }


    public void loop(){
        RawPacket p = null;
        try {
            while( (p = in.readPacket()) != null ){
                process(p);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private byte[] md5(byte[] data){
        try {
            MessageDigest dist = MessageDigest.getInstance("md5");
            dist.update(data);
            return dist.digest();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }


    public static void main(String[] args) throws IOException {
        RtxClient client = new RtxClient();
        client.login("192.168.1.214", 8000, "cody", "1234");
        client.loop();
    }

//    public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
//        MessageDigest dist = MessageDigest.getInstance("md5");
//        String pass ="1234";
//        dist.update(pass.getBytes());
//        byte[] b = dist.digest();
//        System.out.println(util.Util.convertByteToHexString(b));
//
//        dist = MessageDigest.getInstance("md5");
//        byte[] dd = new byte[0x10];
//        Arrays.fill(dd, (byte)0);
//        System.arraycopy(pass.getBytes(), 0, dd, 0, pass.getBytes().length);
//
//        dist.update(dd);
//        b = dist.digest();
//        System.out.println(util.Util.convertByteToHexString(b));
//
//
//    }
}
